package com.xiangxiao.rpan.gateway.filter;

import com.alibaba.fastjson.JSONObject;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.xiangxiao.rpan.gateway.Application;
import com.xiangxiao.rpan.gateway.config.Constants;
import com.xiangxiao.rpan.gateway.dto.ResponseBodyDto;
import com.xiangxiao.rpan.gateway.enums.HttpStatusCode;
import com.xiangxiao.rpan.gateway.enums.ResponseCode;
import com.xiangxiao.rpan.gateway.security.UserInfo;
import com.xiangxiao.rpan.gateway.security.UserVo;
import com.xiangxiao.rpan.gateway.service.TokenService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

/**
 * @auther xiangxiao
 * @email 573768011@qq.com
 * @data 2023/5/5 10:28
 */
@Component
public class AccessFilter extends ZuulFilter {
  private static Logger logger = LoggerFactory.getLogger(AccessFilter.class);
  public static Set<String> UNCHECK_URL_LIST = new HashSet<>(16);
  /**
   * 不过滤的url, 从application.yml中获得
   */
  public static final String URL_IGNORE_FILTER = "urls.without.filter";
  /**
   * url访问控制开关
   */
  public static final String URL_ACCESS_TOGGLE = "url.access.toggle";
  /**
   * options请求模式
   * */
  public static final String METHOD_OPTIONS = "OPTIONS";
  /**
   * 授权信息key
   */
  public static final String AUTHORIZATION_KEY = "Zfile-Token";
  /**
   * 授权信息头
   */
  public static final String AUTHORIZATION_BEARER = "Bearer";
  /**
   * token失效时间是否在操作时自动延长,默认不延长：false
   */
  public static final String TOKEN_EXPIRE_OVERTIME = "token_expire_overtime";
  /**
   * 登出接口
   */
  public static final String LOGOFF = "/ifaas-authority/logoff/1.0";
  @Autowired
  private StringRedisTemplate redisTemplate;
  @Autowired
  private Environment environment;
  @Autowired
  private TokenService tokenService;
  @Value("${token.expire.extend.switch:true}")
  private boolean tokenExpireExtendSwitch;

  public static final int SUPER_USER_ADMIN = 1;

  public AccessFilter(){
    logger.info("初始化accessfilter");
  }
  /**
   * 前置过滤器。
   * 但是在 zuul 中定义了四种不同生命周期的过滤器类型：
   *   1、pre：请求在路由之前被调用,如:身份验证；
   *   2、route：请求在路由时被调用；
   *   3、post：路由到微服务之后执行(在route和error过滤器之后被调用)；
   *   4、error：处理请求发生错误时被调用；
   * @return string
   */
  @Override
  public String filterType() {
    return FilterConstants.PRE_TYPE;
  }

  /**
   * 过滤的优先级，数字越大，优先级越低。
   * @return Long
   */
  @Override
  public int filterOrder() {
    return 0;
  }

  /**
   * 是否执行该过滤器。
   * true：说明需要过滤；
   * false：说明不要过滤；
   * @return Boolean
   */
  @Override
  public boolean shouldFilter() {
    return true;
  }

  @Override
  public Object run() throws ZuulException {
    logger.info("开始执行过滤器");
    try {
      RequestContext ctx = RequestContext.getCurrentContext();
      ctx.getResponse().setContentType("application/json;charset=UTF-8");
      HttpServletRequest request = ctx.getRequest();

      //不需要校验token的url，继续路由
      String requestUrl = request.getRequestURI();
      String requestMethod = request.getMethod();
      String withoutFilterURL = environment.getProperty(URL_IGNORE_FILTER);

      logger.info("请求requestUrl");
      logger.info(requestUrl);

      if (StringUtils.isNotBlank(withoutFilterURL)) {
        String[] urlStrArr = withoutFilterURL.split(",");
        for (String s : urlStrArr) {
          UNCHECK_URL_LIST.add(s);
        }
      };

      // 无需校验token的请求,则直接路由转发过去
      for (String url : UNCHECK_URL_LIST) {
        if (StringUtils.isBlank(url)) {
          continue;
        }

        logger.info("不需要检验token的url");
        logger.info(url);
        if (requestUrl.startsWith(url)) {
          ctx.setSendZuulResponse(true);
          ctx.setResponseStatusCode(HttpStatusCode.SUCCESS.getRespCode());
          ctx.set("isSuccess", true);
          return null;
        }
      };

      //允许OPTIONS请求过去
      String method = request.getMethod();
      if (StringUtils.equalsIgnoreCase(method, METHOD_OPTIONS)) {
        ctx.setSendZuulResponse(true);
        ctx.setResponseStatusCode(HttpStatusCode.SUCCESS.getRespCode());
        ctx.set("isSuccess", true);
        return null;
      }

      // 需要检验token的url
      String authorization = request.getHeader(AUTHORIZATION_KEY);
      Enumeration<String> headNames = request.getHeaderNames();
      while(headNames.hasMoreElements()) {
        String headName = headNames.nextElement();
        System.out.println(headName + ":" + request.getHeader(headName));
      }
      logger.info("authorization is:"+authorization);
      if (StringUtils.isEmpty(authorization) || !authorization.contains(AUTHORIZATION_BEARER) || authorization.trim().equals((AUTHORIZATION_BEARER))) {
        logger.info(ctx.toString());
        logger.info("token为空,拒绝访问");
        accessTokenFail(ctx, "token为空,拒绝访问");
        return null;
      };

      String token = authorization.replace(AUTHORIZATION_BEARER, "").trim();
      if (StringUtils.isBlank(token)) {
        logger.info(ctx.toString());
        logger.info("token为空,拒绝访问");
        accessTokenFail(ctx, "token为空,拒绝访问");
        return null;
      };

      //Redis缓存的token是否已失效
      String userInfoData = redisTemplate.opsForValue().get(token);
      logger.info(String.format("授权信息token为{},从redis里面获取用户信息是否为空userInfoData={}", token, StringUtils.isEmpty(userInfoData)));
      if (StringUtils.isBlank(userInfoData)) {
        logger.info(ctx.toString());
        logger.info("token已失效,拒绝访问");
        accessTokenFail(ctx, "token已失效,拒绝访问");
        return null;
      };

      //token时间是否在操作时自动延长
      String overTime = environment.getProperty(TOKEN_EXPIRE_OVERTIME, "false");
      if (StringUtils.isNotBlank(overTime) && StringUtils.equals("true", overTime.toLowerCase()) && !requestUrl.startsWith(LOGOFF)) {
        boolean isExist = false;
        if(tokenExpireExtendSwitch){
          isExist = redisTemplate.hasKey(Constants.TOKEN_GAP_PREFIX+token);
        }
        logger.info("tokenGapKey isExist:"+isExist);
        if(!isExist){
          //修改token失效时间
          tokenService.updateTokenExpire(token);
        }
      }

      String toggle = environment.getProperty(URL_ACCESS_TOGGLE);
      logger.info(String.format("url={}, 访问开关toggle={}",requestUrl, toggle));
      if (StringUtils.isBlank(toggle) || Boolean.parseBoolean(toggle)) {
        logger.info(String.format("url访问开关打开toggle={}", toggle));
        //校验url
        UserInfo userInfo = JSONObject.parseObject(userInfoData, UserInfo.class);
        logger.info("用户信息为===========", userInfo);
        if (userInfo == null) {
          System.out.println(ctx);
          System.out.println("登录信息不存在,请重新登录");
          accessTokenFail(ctx, "登录信息不存在,请重新登录");
          return null;
        }

        UserVo userVo = userInfo.getUserVo();
        if (userVo == null || userVo.getDeletedFlg() == Constants.ACCOUNT_NOT_EXIST) {
          System.out.println(ctx);
          System.out.println("账号不存在，请联系管理员！");
          accessFail(ctx, "账号不存在，请联系管理员！", 200, ResponseCode.ACCOUNT_NOT_EXIST.getRespCode());
          return null;
        }

        /**********超级管理员不校验权限**********/
        Integer level = userVo.getLevel();
        if (level != null && level == SUPER_USER_ADMIN) {
          ctx.setSendZuulResponse(true);
          ctx.setResponseStatusCode(HttpStatusCode.SUCCESS.getRespCode());
          ctx.set("isSuccess", true);
          return null;
        }
        Calendar calendar = Calendar.getInstance();
        calendar.set(2028, 11, 29);
        Date endTime = userVo.getEndTime() == null ? calendar.getTime() : userVo.getEndTime();
        boolean expired = (userVo.getExpired() != null && userVo.getExpired() == Constants.ACCOUNT_EXPIRED)
            || new Date().compareTo(endTime) >= 0;
        if (userVo.getStatus() == Constants.ACCOUNT_DISABLED || expired) {
          accessFail(ctx, "您的账号已失效，请联系管理员！", 200, ResponseCode.ACCOUNT_DISABLED.getRespCode());
          return null;
        };

//        boolean tryAccess = UrlAccessUtil.tryAccess(userInfo.getOperationList(), requestUrl, requestMethod);
//        if (tryAccess) {
          // 上述所有的校验都通过了,则放行
          ctx.setSendZuulResponse(true);
          ctx.setResponseStatusCode(HttpStatusCode.SUCCESS.getRespCode());
          ctx.set("isSuccess", true);
          return null;
//        }

      }

      return null;
    } catch (Exception e) {
      logger.error("token权限异常", e);
      throw e;
    }
  }

  /**
   * token校验失败
   *
   * @param context
   * @param msg
   */
  private void accessTokenFail(RequestContext context, String msg) {
    accessFail(context, msg, HttpStatusCode.UNAUTHORIZED.getRespCode(), ResponseCode.FORBIDDEN.getRespCode());
  }

  private void accessUrlFail(RequestContext context, String msg) {
    accessFail(context, msg, HttpStatusCode.NOT_FOUND.getRespCode(), ResponseCode.FORBIDDEN.getRespCode());
  }

  private void accessFail(RequestContext context, String msg, int statusCode, int responseCode) {
    context.setSendZuulResponse(false);
    context.setResponseStatusCode(statusCode);
    ResponseBodyDto rb = new ResponseBodyDto(responseCode,
        msg, "");
    context.setResponseBody(JSONObject.toJSONString(rb));
    context.set("isSuccess", false);
  }
}
